"use strict";
/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
let inDebug = false;
function log(str) {
    if (inDebug) {
        console.log(str);
    }
}
const DEFAULT_SIZE = 16;
const NUMBER_THIRTY = 30;
const NUMBER_SIXTEEN = 16;
const NUMBER_TWO = 2;
const NUMBER_FOUR = 4;
const NUMBER_EIGHT = 8;
const NUMBER_075PER = 0.75;
const NUMBER_THIRTYONE = 31;
const NUMBER_TEST_VALUE = 42;
const NUMBER_REPEAT_COUNT = 5;
const NUMBER_BENCHMARK_REPEAT_COUNT = 20;
const NUMBER_TIME_CUT = 1000;
class HashMap {
    calculateCapacity(x) {
        if (x >= 1 << NUMBER_THIRTY) {
            return 1 << NUMBER_THIRTY;
        }
        if (x === 0) {
            return NUMBER_SIXTEEN;
        }
        x = x - 1;
        x |= x >> 1;
        x |= x >> NUMBER_TWO;
        x |= x >> NUMBER_FOUR;
        x |= x >> NUMBER_EIGHT;
        x |= x >> NUMBER_SIXTEEN;
        return x + 1;
    }
    computeHashCode(key) {
        switch (typeof key) {
            case 'undefined':
                return 0;
            case 'boolean':
                return 0;
            case 'number':
                return key;
            case 'string':
                let h = 0;
                let len = key.length;
                for (let index = 0; index < len; index++) {
                    h = (((NUMBER_THIRTYONE * h) | 0) + key.charCodeAt(index)) | 0;
                }
                return h;
            default:
                throw new Error('Internal error: Bad JavaScript value type');
        }
    }
    equals(a, b) {
        if (typeof a !== typeof b) {
            return false;
        }
        switch (typeof a) {
            case 'number':
                return a === b;
            case 'object':
                if (a == null) {
                    return b == null;
                }
            case 'function':
                switch (typeof b) {
                    case 'object':
                    case 'function':
                        return a === b;
                    default:
                        return false;
                }
            default:
                return a === b;
        }
    }
    constructor(capacity = DEFAULT_SIZE, loadFactor = NUMBER_075PER) {
        this.capacity = this.calculateCapacity(capacity);
        this.elementCount = 0;
        this.elementData = new Array(this.capacity);
        this.loadFactor = loadFactor;
        this.modCount = 0;
        this.computeThreshold();
    }
    computeThreshold() {
        this.threshold = (this.elementData.length * this.loadFactor) | 0;
    }
    clear() {
        if (this.elementCount == null) {
            return;
        }
        this.elementCount = 0;
        for (let i = this.elementData.length; i >= 0; i--) {
            this.elementData[i] = null;
        }
        this.modCount++;
    }
    clone() {
        let result = new HashMap(this.elementData.length, this.loadFactor);
        result.putAll(this);
        return result;
    }
    containsValue(value) {
        for (let entry of this.elementData) {
            let currentEntry = entry;
            while (currentEntry != null) {
                if (this.equals(value, currentEntry.value)) {
                    return true;
                }
                currentEntry = entry.next;
            }
        }
        return false;
    }
    entrySet() {
        return new EntrySet(this);
    }
    get(key) {
        let entry = this.getEntry(key);
        if (entry != null) {
            return entry.value;
        }
        return null;
    }
    getEntry(key) {
        let hash = this.computeHashCode(key);
        let index = hash & (this.elementData.length - 1);
        return this.findKeyEntry(key, index, hash);
    }
    findKeyEntry(key, index, keyHash) {
        let entry = this.elementData.length === 0 ? null : this.elementData[index];
        while (entry != null && (entry.origKeyHash !== keyHash || !this.equals(key, entry.key))) {
            entry = entry.next;
        }
        return entry;
    }
    isEmpty() {
        return this.elementCount === 0;
    }
    put(key, value) {
        let hash = this.computeHashCode(key);
        let index = hash & (this.elementData.length - 1);
        let entry = this.findKeyEntry(key, index, hash);
        if (!entry) {
            this.modCount++;
            entry = this.createHashedEntry(key, index, hash);
            this.elementCount += 1;
            if (this.elementCount > this.threshold) {
                this.rehash(0);
            }
        }
        let result = entry.value;
        entry.value = value;
        // if (inDebug) {
        //   if (key % 10000 == 0 ) {
        //       log("put:" + entry.toString());
        //   }
        // }
        return result;
    }
    createHashedEntry(key, index, hash) {
        let entry = new Entry(key, hash, null);
        entry.next = this.elementData[index];
        this.elementData[index] = entry;
        return entry;
    }
    putAll(map) {
        if (map.isEmpty()) {
            return;
        }
        let iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            let entry = iterator.next();
            this.put(entry.key, entry.value);
        }
    }
    rehash(capacity) {
        if (capacity <= 0) {
            capacity = this.elementData.length;
        }
        let length = this.calculateCapacity(!capacity ? 1 : capacity << 1);
        let newData = new Array(length);
        for (let i = 0; i < this.elementData.length; ++i) {
            let entry = this.elementData[i];
            this.elementData[i] = null;
            while (entry) {
                let index = entry.origKeyHash & (length - 1);
                let next = entry.next;
                entry.next = newData[index];
                newData[index] = entry;
                entry = next;
            }
        }
        this.elementData = newData;
        this.computeThreshold();
    }
    remove(key) {
        let entry = this.removeEntryForKey(key);
        if (entry == null) {
            return null;
        }
        return entry.value;
    }
    removeEntry(entry) {
        let index = entry.origKeyHash & (this.elementData.length - 1);
        let current = this.elementData[index];
        if (current === entry) {
            this.elementData[index] = entry.next;
        }
        else {
            while (current.next !== entry) {
                current = current.next;
            }
            current.next = entry.next;
        }
        this.modCount++;
        this.elementCount--;
    }
    removeEntryForKey(key) {
        let hash = this.computeHashCode(key);
        let index = hash & (this.elementData.length - 1);
        let entry = this.elementData[index];
        let last = null;
        while (entry != null && !(entry.origKeyHash === hash && this.equals(key, entry.value))) {
            last = entry;
            entry = entry.next;
        }
        if (entry == null) {
            return null;
        }
        if (last == null) {
            this.elementData[index] = entry.next;
        }
        else {
            last.next = entry.next;
        }
        this.modCount++;
        this.elementCount--;
        return entry;
    }
    size() {
        return this.elementCount;
    }
    values() {
        return new ValueCollection(this);
    }
}
class Entry {
    constructor(key, hash, value) {
        this.key = key;
        this.value = value;
        this.origKeyHash = hash;
    }
    clone() {
        let result = new Entry(this.key, this.value, this.origKeyHash);
        if (this.next != null) {
            result.next = this.next.clone();
        }
        return result;
    }
    toString() {
        return this.key + '=' + this.value;
    }
    getKey() {
        return this.key;
    }
    getValue() {
        return this.value;
    }
}
class AbstractMapIterator {
    constructor(map) {
        this.expectedModCount = 0;
        this.futureEntry = null;
        this.currentEntry = null;
        this.prevEntry = null;
        this.position = 0;
        this.associatedMap = map;
        this.expectedModCount = map.modCount;
    }
    hasNext() {
        if (this.futureEntry != null) {
            return true;
        }
        while (this.position < this.associatedMap.elementData.length) {
            if (!this.associatedMap.elementData[this.position]) {
                this.position++;
            }
            else {
                return true;
            }
        }
        return false;
    }
    checkConcurrentMod() {
        if (this.expectedModCount !== this.associatedMap.modCount) {
            throw new Error('Concurrent HashMap modification detected');
        }
    }
    makeNext() {
        this.checkConcurrentMod();
        if (this.hasNext() == null) {
            throw new Error('No such element');
        }
        if (this.futureEntry == null) {
            this.currentEntry = this.associatedMap.elementData[this.position++];
            this.futureEntry = this.currentEntry.next;
            this.prevEntry = null;
            return;
        }
        if (this.currentEntry != null) {
            this.prevEntry = this.currentEntry;
        }
        this.currentEntry = this.futureEntry;
        this.futureEntry = this.futureEntry.next;
    }
    remove() {
        this.checkConcurrentMod();
        if (this.currentEntry == null) {
            throw new Error('Illegal state');
        }
        if (this.prevEntry == null) {
            let index = this.currentEntry.origKeyHash & (this.associatedMap.elementData.length - 1);
            this.associatedMap.elementData[index] = this.associatedMap.elementData[index].next;
        }
        else {
            this.prevEntry.next = this.currentEntry.next;
        }
        this.currentEntry = null;
        this.expectedModCount++;
        this.associatedMap.modCount++;
        this.associatedMap.elementCount--;
    }
}
class ValueIterator extends AbstractMapIterator {
    next() {
        this.makeNext();
        return this.currentEntry;
    }
}
class EntrySet {
    constructor(map) {
        this.associatedMap = map;
    }
    size() {
        return this.associatedMap.elementCount;
    }
    clear() {
        this.associatedMap.clear();
    }
    iterator() {
        return new ValueIterator(this.associatedMap);
    }
}
class ValueCollection {
    constructor(map) {
        this.associatedMap = map;
    }
    contains(object) {
        return this.associatedMap.containsValue(object);
    }
    size() {
        return this.associatedMap.elementCount;
    }
    clear() {
        this.associatedMap.clear();
    }
    iterator() {
        return new ValueIterator(this.associatedMap);
    }
}
/*
 * @State
 * @Tags Jetstream2
 */
class Benchmark {
    run() {
        let map = new HashMap();
        let COUNT = 90000;
        for (let i = 0; i < COUNT; i++) {
            map.put(i, NUMBER_TEST_VALUE);
        }
        let result = 0;
        for (let j = 0; j < NUMBER_REPEAT_COUNT; j++) {
            for (let i = 0; i < COUNT; i++) {
                result += map.get(i);
                // if (inDebug) {
                //   if (i % 10000 == 0) {
                //     let value = map.get(i);
                //     log("get:" + i + "=" + value);
                //   }
                // }
            }
        }
        let keySum = 0;
        let valueSum = 0;
        let iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            let entry = iterator.next();
            keySum += entry.key;
            valueSum += entry.value;
        }
    }
    /**
     * @Benchmark
     */
    runIteration() {
        let startTime = currentTimestamp16();
        for (let j = 0; j < NUMBER_BENCHMARK_REPEAT_COUNT; ++j) {
            this.run();
        }
        let endTime = currentTimestamp16();
        let time = endTime - startTime;
        console.log(`hash-map: ms = ${time}`);
    }
}
new Benchmark().runIteration();
function currentTimestamp16() {
    return Date.now();
}
